/*
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ST_EXT-S_UIMAST.
 *
 * The Initial Developer of the Original Code is
 * University of Rome, Tor Vergata
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 */

/**
 * A type system.
 */
function TypeSystem()
{		
	// The type dictionary. It let us to access quickly to the defined types.
	this.types = {};
	
	//// Defines built-in types
	
	// uima.cas.TOP
	this.uima_cas_TOP = new Type("uima.cas.TOP",null,null,null); 
	this.types["uima.cas.TOP"] = this.uima_cas_TOP;
	
	// uima.cas.Boolean
	this.uima_cas_Boolean = new Type("uima.cas.Boolean",null,this.uima_cas_TOP,null); 
	this.types["uima.cas.Boolean"] = this.uima_cas_Boolean;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Boolean);

	// uima.cas.Byte
	this.uima_cas_Byte = new Type("uima.cas.Byte",null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Byte"] = this.uima_cas_Byte;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Byte);
	
	// uima.cas.Short
	this.uima_cas_Short = new Type("uima.cas.Short", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Short"] = this.uima_cas_Short;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Short);

	// uima.cas.Integer
	this.uima_cas_Integer = new Type("uima.cas.Integer", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Integer"] = this.uima_cas_Integer;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Integer);

	// uima.cas.Long
	this.uima_cas_Long = new Type("uima.cas.Long", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Long"] = this.uima_cas_Long;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Long);

	// uima.cas.Float
	this.uima_cas_Float = new Type("uima.cas.Float", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Float"] = this.uima_cas_Float;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Float);
	
	// uima.cas.Double
	this.uima_cas_Double = new Type("uima.cas.Double", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.Double"] = this.uima_cas_Double;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Double);
	
	// uima.cas.String
	this.uima_cas_String = new Type("uima.cas.String", null, this.uima_cas_TOP, null); 
	this.types["uima.cas.String"] = this.uima_cas_String;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_String);			
	
	// uima.cas.ArrayBase
	this.uima_cas_ArrayBase = new Type("uima.cas.ArrayBase", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.ArrayBase"] = this.uima_cas_ArrayBase;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_ArrayBase);

	// uima.cas.FSArray
	this.uima_cas_FSArray = new Type("uima.cas.FSArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.FSArray"] = this.uima_cas_FSArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_FSArray);

	// uima.cas.BooleanArray
	this.uima_cas_BooleanArray = new Type("uima.cas.BooleanArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.BooleanArray"] = this.uima_cas_BooleanArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_BooleanArray);
	
	// uima.cas.ByteArray
	this.uima_cas_ByteArray = new Type("uima.cas.ByteArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.ByteArray"] = this.uima_cas_ByteArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_ByteArray);

	// uima.cas.ShortArray
	this.uima_cas_ShortArray = new Type("uima.cas.ShortArray", null,this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.ShortArray"] = this.uima_cas_ShortArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_ShortArray);

	// uima.cas.IntegerArray
	this.uima_cas_IntegerArray = new Type("uima.cas.IntegerArray", null,this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.IntegerArray"] = this.uima_cas_IntegerArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_IntegerArray);

	// uima.cas.LongArray
	this.uima_cas_LongArray = new Type("uima.cas.LongArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.LongArray"] = this.uima_cas_LongArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_LongArray);

	// uima.cas.FloatArray
	this.uima_cas_FloatArray = new Type("uima.cas.FloatArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.FloatArray"] = this.uima_cas_FloatArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_FloatArray);

	// uima.cas.DoubleArray
	this.uima_cas_DoubleArray = new Type("uima.cas.DoubleArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.DoubleArray"] = this.uima_cas_DoubleArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_DoubleArray);

	// uima.cas.StringArray
	this.uima_cas_StringArray = new Type("uima.cas.StringArray", null, this.uima_cas_ArrayBase,null); 
	this.types["uima.cas.StringArray"] = this.uima_cas_StringArray;
	this.uima_cas_ArrayBase.subtypes.push(this.uima_cas_StringArray);

	// uima.cas.ListBase
	this.uima_cas_ListBase = new Type("uima.cas.ListBase", null, this.uima_cas_TOP,null); 
	this.types["uima.cas.ListBase"] = this.uima_cas_ListBase;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_ListBase);

	// uima.cas.FSList
	this.uima_cas_FSList = new Type("uima.cas.FSList", null, this.uima_cas_ListBase,null); 
	this.types["uima.cas.FSList"] = this.uima_cas_FSList;
	this.uima_cas_ListBase.subtypes.push(this.uima_cas_FSList);

	// uima.cas.EmptyFSList
	this.uima_cas_EmptyFSList = new Type("uima.cas.EmptyFSList", null, this.uima_cas_FSList,null); 
	this.types["uima.cas.EmptyFSList"] = this.uima_cas_EmptyFSList;
	this.uima_cas_FSList.subtypes.push(this.uima_cas_EmptyFSList);
	
	// uima.cas.NonEmptyFSList
	this.uima_cas_NonEmptyFSList = new Type("uima.cas.NonEmptyFSList", null, this.uima_cas_FSList,null); 
	this.types["uima.cas.NonEmptyFSList"] = this.uima_cas_NonEmptyFSList;
	this.uima_cas_FSList.subtypes.push(this.uima_cas_NonEmptyFSList);

	this.uima_cas_NonEmptyFSList.features.push(new Feature("head",null, this.uima_cas_TOP,null,null));
	this.uima_cas_NonEmptyFSList.features.push(new Feature("tail",null, this.uima_cas_FSList,null,null));

	// uima.cas.FloatList
	this.uima_cas_FloatList = new Type("uima.cas.FloatList",null,this.uima_cas_ListBase,null); 
	this.types["uima.cas.FloatList"] = this.uima_cas_FloatList;
	this.uima_cas_ListBase.subtypes.push(this.uima_cas_FloatList);

	// uima.cas.EmptyFloatList
	this.uima_cas_EmptyFloatList = new Type("uima.cas.EmptyFloatList",null, this.uima_cas_FloatList,null); 
	this.types["uima.cas.EmptyFloatList"] = this.uima_cas_EmptyFloatList;
	this.uima_cas_FloatList.subtypes.push(this.uima_cas_EmptyFloatList);
	
	// uima.cas.NonEmptyFloatList
	this.uima_cas_NonEmptyFloatList = new Type("uima.cas.NonEmptyFloatList", null, this.uima_cas_FloatList,null); 
	this.types["uima.cas.NonEmptyFloatList"] = this.uima_cas_NonEmptyFloatList;
	this.uima_cas_FloatList.subtypes.push(this.uima_cas_NonEmptyFloatList);

	this.uima_cas_NonEmptyFloatList.features.push(new Feature("head",null, this.uima_cas_Float,null,null));
	this.uima_cas_NonEmptyFloatList.features.push(new Feature("tail",null, this.uima_cas_FloatList,null,null));

	// uima.cas.IntegerList
	this.uima_cas_IntegerList = new Type("uima.cas.IntegerList", null, this.uima_cas_ListBase,null); 
	this.types["uima.cas.IntegerList"] = this.uima_cas_IntegerList;
	this.uima_cas_ListBase.subtypes.push(this.uima_cas_IntegerList);

	// uima.cas.EmptyIntegerList
	this.uima_cas_EmptyIntegerList = new Type("uima.cas.EmptyIntegerList", null, this.uima_cas_IntegerList,null); 
	this.types["uima.cas.EmptyIntegerList"] = this.uima_cas_EmptyIntegerList;
	this.uima_cas_IntegerList.subtypes.push(this.uima_cas_EmptyIntegerList);
	
	// uima.cas.NonEmptyIntegerList
	this.uima_cas_NonEmptyIntegerList = new Type("uima.cas.NonEmptyIntegerList", null, this.uima_cas_IntegerList,null); 
	this.types["uima.cas.NonEmptyIntegerList"] = this.uima_cas_NonEmptyIntegerList;
	this.uima_cas_IntegerList.subtypes.push(this.uima_cas_NonEmptyIntegerList);

	this.uima_cas_NonEmptyIntegerList.features.push(new Feature("head",null, this.uima_cas_Integer,null,null));
	this.uima_cas_NonEmptyIntegerList.features.push(new Feature("tail",null, this.uima_cas_IntegerList,null,null));

	// uima.cas.StringList
	this.uima_cas_StringList = new Type("uima.cas.StringList", null, this.uima_cas_ListBase,null); 
	this.types["uima.cas.StringList"] = this.uima_cas_StringList;
	this.uima_cas_ListBase.subtypes.push(this.uima_cas_StringList);

	// uima.cas.EmptyStringList
	this.uima_cas_EmptyStringList = new Type("uima.cas.EmptyStringList", null, this.uima_cas_StringList,null); 
	this.types["uima.cas.EmptyStringList"] = this.uima_cas_EmptyStringList;
	this.uima_cas_StringList.subtypes.push(this.uima_cas_EmptyStringList);
	
	// uima.cas.NonEmptyStringList
	this.uima_cas_NonEmptyStringList = new Type("uima.cas.NonEmptyStringList", null, this.uima_cas_StringList,null); 
	this.types["uima.cas.NonEmptyStringList"] = this.uima_cas_NonEmptyStringList;
	this.uima_cas_StringList.subtypes.push(this.uima_cas_NonEmptyStringList);

	this.uima_cas_NonEmptyStringList.features.push(new Feature("head",null, this.uima_cas_String,null,null));
	this.uima_cas_NonEmptyStringList.features.push(new Feature("tail",null, this.uima_cas_StringList,null,null));
	
	// uima.cas.Sofa
	this.uima_cas_Sofa = new Type("uima.cas.Sofa",null,this.uima_cas_TOP,null); 
	this.types["uima.cas.Sofa"] = this.uima_cas_Sofa;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_Sofa);
	
	this.uima_cas_Sofa.features.push(new Feature("sofaNum",null, this.uima_cas_Integer,null,null));
	this.uima_cas_Sofa.features.push(new Feature("sofaID",null, this.uima_cas_String,null,null));
	this.uima_cas_Sofa.features.push(new Feature("mimeType",null, this.uima_cas_String,null,null));
	this.uima_cas_Sofa.features.push(new Feature("sofaArray",null, this.uima_cas_TOP,null,null));
	this.uima_cas_Sofa.features.push(new Feature("sofaString",null, this.uima_cas_String,null,null));
	this.uima_cas_Sofa.features.push(new Feature("sofaURI",null, this.uima_cas_String,null,null));

	// uima.cas.AnnotationBase
	this.uima_cas_AnnotationBase = new Type("uima.cas.AnnotationBase",null,this.uima_cas_TOP,null); 
	this.types["uima.cas.AnnotationBase"] = this.uima_cas_AnnotationBase;
	this.uima_cas_TOP.subtypes.push(this.uima_cas_AnnotationBase);			

	this.uima_cas_AnnotationBase.features.push(new Feature("sofa",null, this.uima_cas_Sofa,null,null));
				
	// uima.tcas.Annotation
	this.uima_tcas_Annotation = new Type("uima.tcas.Annotation",null,this.uima_cas_AnnotationBase,null); 
	this.types["uima.tcas.Annotation"] = this.uima_tcas_Annotation;
	this.uima_cas_AnnotationBase.subtypes.push(this.uima_tcas_Annotation);

	this.uima_tcas_Annotation.features.push(new Feature("begin",null, this.uima_cas_Integer,null,null));
	this.uima_tcas_Annotation.features.push(new Feature("end",null, this.uima_cas_Integer,null,null));

	// uima.tcas.DocumentAnnotation
	this.uima_tcas_DocumentAnnotation = new Type("uima.tcas.DocumentAnnotation",null, this.uima_tcas_Annotation,null); 
	this.types["uima.tcas.DocumentAnnotation"] = this.uima_tcas_DocumentAnnotation;
	this.uima_tcas_Annotation.subtypes.push(this.uima_tcas_DocumentAnnotation);

	this.uima_tcas_DocumentAnnotation.features.push(new Feature("language",null, this.uima_cas_String,null,null));
	
	// The descriptors loaded
	this.descriptors = [];
	this.descriptors.push(new TypeSystemDescriptor(null));
	
	// Error flag
	this.hasError = false;
}

// The name property
TypeSystem.prototype.__defineSetter__("name", function(newName){this.descriptors[0].name = newName; return newName;});	
TypeSystem.prototype.__defineGetter__("name", function(){return this.descriptors[0].name;});	

// The description property
TypeSystem.prototype.__defineSetter__("description", function(newDescription){this.descriptors[0].description = newDescription; return newDescription;});	
TypeSystem.prototype.__defineGetter__("description", function(){return this.descriptors[0].description;});	

// The version property
TypeSystem.prototype.__defineSetter__("version", function(newVersion){this.descriptors[0].version = newVersion; return newVersion;});	
TypeSystem.prototype.__defineGetter__("version", function(){return this.descriptors[0].version;});	

// The vendor property
TypeSystem.prototype.__defineSetter__("vendor", function(newVendor){this.descriptors[0].vendor = newVendor; return newVendor;});	
TypeSystem.prototype.__defineGetter__("vendor", function(){return this.descriptors[0].vendor;});	

// Note that the predicates isX are guarateed to work only after the type references were resolved during the parsing
	
/**
 * Checks if a type is primitive.
 * @param type the type to test
 * @return true iff the type is primitive
 */
TypeSystem.prototype.isPrimitive = function(type) {
	const primitives = { "uima.cas.Boolean" : true,
	                     "uima.cas.Byte"    : true,
	                     "uima.cas.Short"   : true,
	                     "uima.cas.Integer" : true,
	                     "uima.cas.Long"    : true,
	                     "uima.cas.Float"   : true,
	                     "uima.cas.Double"  : true,
	                     "uima.cas.String"  : true };	
		
	return type !== null && (primitives[type.name] || false);		
};	

/**
 * Checks if a type is builtin.
 * @param type the type to test
 * @return true iff the type is builtin
 */
TypeSystem.prototype.isBuiltin = function(type) {
	return (type && type.descriptor == null) || false;
};

/**
 * Checks if a type is templatized.
 * @param type the type to test
 * @return true iff the type is templatized
 */
TypeSystem.prototype.isTemplatized = function(type) {
	return (type && (type == this.uima_cas_FSList || type == this.uima_cas_FSArray)) || false;
};

/**
 * Checks if a type is an array.
 * @param type the type to test
 * @return true iff the type is an array
 */
TypeSystem.prototype.isArray = function(type) {
	return (type && (type == this.uima_cas_FSArray)) || false;
};

/**
 * Checks if a type is enumerated.
 * @param type the type to test
 * @return true iff the type is enumerated
 */
TypeSystem.prototype.isEnumerated = function(type) {
	// excludes null
	return type !== null && (type.supertype == this.uima_cas_String);
};

/**
 * Checks if a type is final.
 * @param type the type to test
 * @return true iff the type is final
 */
TypeSystem.prototype.isFinal = function(type) {
	return this.isEnumerated(type) || (this.isPrimitive(type) && type != this.uima_cas_String);
};

/**
 * Checks if a type is feature final, ie. cannot have any feature.
 * @param type the type to test
 * @return true iff the type is feature final
 */
TypeSystem.prototype.isFeatureFinal = function(type) {
	// Actually considers feature final primitive types and enumerated ones
	return this.isPrimitive(type) || this.isEnumerated(type);
};

/**
 * Checks if a type is writeable. Only the types defined in the first
 * descriptor are writeable.
 * @param type the type to test
 * @return true iff the type is writeable
 */
TypeSystem.prototype.isWriteable = function(type) {
	return (type !== null && this.descriptors[0] && type.descriptor == this.descriptors[0] && true) || false;
};

/**
 * Gets a type with given name.
 * @param name the type name
 * @return a type or null if there isn't one
 */
TypeSystem.prototype.getType = function(name) {
	if(this.types[name])
		return this.types[name];
		
	var t;
	for(var i = 0 ; i < this.descriptors.length ; ++i) {
		t = this.descriptors[i].types[name];
		if(t)
			return t;
	}
	
	return null;
};

/**
 * Gets a feature of a type. Considers the inherited features before the declared ones.
 * @param type the type to inspect
 * @param name the feature name
 * @return the feature
 */
TypeSystem.prototype.getFeature = function(type, name) {			
	var supertype = type.supertype;
				
	if(!supertype)
		return type.getDeclaredFeature(name);
	else
		return (this.getFeature(supertype,name) || type.getDeclaredFeature(name));
};

/**
 * Gets an iterator on the types defined by this type system.
 * @return an iterator
 */
TypeSystem.prototype.getTypeIterator = function() {
	var ts = this;	// a reference to the type system
					
	return ({

		_stack : [ts.uima_cas_TOP],

		hasNext : function() {
			return this._stack.length != 0;
		},
		
		next   : function() {
			var t = this._stack.pop();
			
			this._stack = this._stack.concat(t.subtypes);
									
			return t;
		}			
	});		
};

/**
 * Gets an iterator on the features of a given type.
 * @param type the type to consider
 * @return an iterator
 */
TypeSystem.prototype.getFeatureIterator = function(type) {
	var ts = this;	// a reference to the type system
				
	return ({
		_currentType : type,
		_currentPosition : 0,
		
		hasNext : function() {
			while(this._currentType) {
				if(this._currentPosition < this._currentType.features.length) {
					return true;
				} else {
					this._currentPosition = 0;
					if(this._currentType.supertype) {
						this._currentType = this._currentType.supertype;
					} else {
						this._currentType = null;
					}
				}
			}
			
			return false;
		},
		
		next : function() {
			var f = this._currentType.features[this._currentPosition];
			
			this.name = f.name;
			this.range = f.rangeType.name + (f.elementType ? "[" + f.elementType.name + "]" : "");
			this.multipleReferencesAllowed = (f.multipleReferencesAllowed != null ? f.multipleReferencesAllowed : "");
			this.domain = this._currentType.name;
			
			this._currentPosition++;
		}
	});
};

/**
 * Returns an object containing a human readable description of the
 * feature attributes.
 * @param feature a feature
 * @return a format object
 */
TypeSystem.prototype.formatFeature = function(feature) {
	return ({
			name : feature.name,
			range : feature.rangeType.name + (feature.elementType ? "[" + feature.elementType.name + "]" : ""),
			multipleReferencesAllowed : (feature.multipleReferencesAllowed != null ? feature.multipleReferencesAllowed : "")				
	});
};

/**
 * Gets an iterator on an enumerated type allowed values.
 * @param type a type
 * @return an iterator
 */
TypeSystem.prototype.getAllowedValuesIterator = function(type) {			
	return ({
		_position : 0,
		
		hasNext : function() {
			return this._position < type.allowedValues.length;
		},
		
		next    : function() {
			this.value = type.allowedValues[this._position][0];
			this.description = type.allowedValues[this._position][1];
			
			++this._position;
		} 
	});
};

/**
 * Adds a subtype.
 * @param type the type to extend
 * @param newName the name of the subtype
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.addSubtype = function(type, newName) {
	if(type == null) {
		this.error(null,null, "Cannot subclass the null type");
		return false;
	}
	
	// Checks name validity
	if(!Type.isValidName(newName)) {
		this.error(null, null,"\""+newName+"\" isn't a valid type name");
		return false;
	}			
	
	// Checks the name uniqueness
	if(this.getType(newName) != null) {
		this.error(null, null,"The type name \"" + newName + "\" is already in use");
		return false;
	}
	
	// The first descriptor
	var desc = this.descriptors[0];

	// Creates the new type
	var newType = new Type(newName, null, type, desc);
	
	// Adds the new type to the list of subtypes of its parent
	type.subtypes.push(newType);
	
	// Adds the type to the first descriptor lookup table
	desc.types[newName] = newType;				
				
	return true;
};

/**
 * Renames a type.
 * @param type the type to rename
 * @param newName the type new name
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.renameType = function(type, newName) {
	if(type == null) {
		this.error(null, null,"Cannot rename the null type");
		return false;
	}
	
	// Checks name validity
	if(!Type.isValidName(newName)) {
		this.error(null, null,"\""+newName+"\" isn't a valid type name");
		return false;
	}			
	
	// Checks the name uniqueness
	if(this.getType(newName) != null) {
		this.error(null, null,"The type name \"" + newName + "\" is already in use");
		return false;
	}
				
	// Replaces the old name
	var oldName = type.name;
	type.name = newName;
	
	// If a type is renamed, then it is necessary to
	// update the index
	delete(type.descriptor.types[oldName]);
	type.descriptor.types[newName] = type;
	
	return true;
};

/**
 * Deletes a type.
 * @param type the type to delete
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.deleteType = function(type) {
	if(type == null) {
		this.error(null, null,"Cannot delete the null type");
		return false;
	}
	
	// Enumerates the types in the tree rooted in type
	// and checks they are writable
	var types = [type];
	
	// Traverse the tree, until reaches the yield
	for(var i = 0 ; i < types.length ; ++i) {
		if(!this.isWriteable(types[i])) {
			this.error(null, null,"The type \"" + types[i].name + "\" isn't writeable");
			return false;
		} else {
			types = types.concat(types[i].subtypes);
		}
	}	
						
	// Iterates over the types, checking that every referencing type is writable
	for(var i = 0 ; i < types.length ; ++i) {
		for(var j in types[i].references) {
		
			var r = types[i].references[j].type;
			if(!this.isWriteable(r)) {
				this.error(null, null,"The type \"" + (r.name) + "\" isn't writeable");						
				return false;
			}
		}
	}		
		
	// Iterates over the types, deleting the features which refer them
	for(var i = 0 ; i < types.length ; ++i) {
		for(var j in types[i].references) {	
			var r = types[i].references[j].type;
								
			// The number of preserved features
			var count = 0;
			
			for(var h = 0 ; h < r.features.length ; ++h) {
				if(r.features[h].rangeType != types[i] && r.features[h].elementType != types[i]) {
					// Copies a feature not referring to the current type
					if(h != count)
						r.features[count] = r.features[h];
					
					++count;
				} else {
					// since it has removed a referring feature,
					// it must toggle a reference				
					types[i].toggleReference(r);
				}		
			}
			
			// Resizes the features array length
			r.features.length = count;
		}
	}

	// Removes the root type from its parent subtypes list
	var st = type.supertype;
	for(var i = 0 ; i < st.subtypes.length ; ++i) {
		if(st.subtypes[i] == type) {
			st.subtypes.splice(i,1);
			break;
		}
	}	
			
	// Iterates over the types to finalize the process
	for(var i = 0 ; i < types.length ; ++i) {
		// Deletes the type from the types list
		delete(types[i].descriptor.types[types[i].name]);
						
		// Removes every references generated by that type
		for(var h = 0 ; h < types[i].features.length ; ++h) {	// iterates over the declared features
			var f = types[i].features[h];
			var rangeType = f.rangeType;
			var elementType = f.elementType;
			
			// Delete in the range type every references from this type
			if(this.getType(rangeType.name))
				rangeType.toggleAllReferences(types[i]);
			
			// Deletes in the element type (if any) every references from this type
			if(elementType !== null && this.getType(elementType.name))
				elementType.toggleAllReferences(types[i]);
				
			// We need to check that the range type and the element type is found
			// since it could be a deleted type higher in the inheritance tree
		}	
	}
				
	return true;
};

/**
 * Adds a feature to the given type.
 * @param domain the type which the feature will be added to
 * @param name the feature name
 * @param description the feature description
 * @param rangeType the range type
 * @param elementType the element type (if null, it will be replaced with uima.cas.TOP)
 * @param multipleReferencesAllowed determines if multiple references are allowed
 * @return thew newly added feature or false if there is an error
 */
TypeSystem.prototype.addFeature = function(domain, name, description, rangeType, elementType, multipleReferencesAllowed )
{	
	// Checks the name validity
	if(!Feature.isValidShortName(name)) {
		this.error(null, null,"Cannot add the feature.\nThe proposed name isn't valid");
		return false;
	}	
		
	// Checks the domain
	if(domain == null) {
		this.error(null, null,"Cannot add the feature.\nYou need to specify an existing type as domain");
		return false;
	}
				
	// Checks the range
	if(rangeType == null) {
		this.error(null, null,"Cannot add the feature.\nYou need to specify an existing type as range");
		return false;
	}	
	
	// Checks the element type
	if(this.isTemplatized(domain) && (elementType == null)) {
		this.error(null, null,"Cannot add the feature.\nYou need to specify an existing element type");
		return false;
	}	
	
	// Checks if the domain has an homonym feature
	if(domain.getDeclaredFeature(name)) {
		this.error(null, null,"Cannot add the feature.\nThe domain defines an homonym feature");			
		return false;
	}
				
	// Gets the most derived types from the domain type
	var mostDerived = [domain];
	
	for(var i = 0 ; i < mostDerived.length ; ++i ) {
		if(mostDerived[i].subtypes.length) {	// if the current type has children, removes it, since it isn't the most derived
			// Appends the subtypes
			mostDerived = mostDerived.concat(mostDerived[i].subtypes);
			
			// Deletes the current type, since it has subtypes
			delete(mostDerived[i]);
		}
	}
				
	// Creates the feature
	var feature = new Feature(name, description, rangeType, elementType, multipleReferencesAllowed ? true : false);
	
	// Normalizes the features
	this.normalizeFeature(feature);		
	
	// Checks that every most derived type
	// either hasn't an homonym feature or it is equivalent
	for(var i in mostDerived) {
		var t = mostDerived[i];	// a type
		
		var f = this.getFeature(t,name);	// gets the feature, or null if it doesn't exist
		
		if(f && !f.equals(feature)) {
			this.error(null, null,"Cannot add the feature.\nThere is an incompatible definition in the subtree rooted at the given type");
			return false;
		}
	}

	// Adds the new feature to the domain features list
	domain.features.push(feature);
							
	// Adds the references to the range type and the element type
	feature.rangeType.addReference(domain);			
	
	if(feature.elementType)
		feature.elementType.addReference(domain);
	
	return feature;
};

/**
 * Normalizes a feature. If the range type isn't templatized, then
 * discards the element type. If the range type isn't an array,
 * then discards the allowMultipleRefereces. If the range type is
 * uima.cas.FSArray, and the element type is primitive, then replaces
 * the range type with the appropriate array of primitive values
 * @param feature the feature to normalize
 */
TypeSystem.prototype.normalizeFeature = function(feature) {

	// Gets the feature range
	var r = feature.rangeType;
								
	// If the feature isn't templatized (and neither an array type)
	if(!this.isTemplatized(r)) {
		feature.elementType = null;	// discards the element type
		feature.multipleReferencesAllowed = null;	// discards multiple references allowed
	} else if(r == this.uima_cas_FSArray) {				
										
		// Gets the element type
		var e = feature.elementType;
														
		// If the element type is primitive
		if(this.isPrimitive(e)){
			// Removes the element type
			feature.elementType = null;
			
			// Replaces it with the appropriare array type: uima.cas.<short name>Array
			var shortName = Type.getShortName(e.name);
			
			feature.rangeType = this.getType("uima.cas." + shortName + "Array");
		}
	}
};

/**
 * Deletes a feature declared in a type.
 * @param type the type declaring the feature
 * @param name the name of the feature to remove
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.deleteFeature = function(type, name)
{
	if(type == null) {
		this.error(null, null,"Cannot delete a feature from the null type");
		return false;
	}
	
	if(!this.isWriteable(type)) {
		this.error(null, null,"The type \"" + type.name + "\" isn't writeable");
		return false;
	}
				
	// Finds the feature to delete
	for(var i = 0 ; i < type.features.length ; ++i) {
		var f = type.features[i];
		
		// If the feature is found
		if(f.name == name) {
			// Toggles references in rangeType and elementType
			f.rangeType.toggleReference(type);					
			
			if(f.elementType !== null)	// checks that the feature has an element type
				f.elementType.toggleReference(type);								
		
			// removes this feature
			type.features.splice(i,1);
		
			return true;
		}
	}
	
	this.error(null, null,"The type \"" + type.name + "\" hasn't the feature \"" + name + "\"");
	return false;
};

/**
 * Adds an allowed value to a type.
 * @param type a type
 * @param value the allowed value
 * @param description the value description
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.addValue = function(type, value, description)
{			
	if(type == null) {
		this.error(null, null,"Cannot manipulate the null type");
		return false;
	}
	
	if(!this.isWriteable(type)) {
		this.error(null, null,"The type \"" + type.name + "\" isn't writeable");
		return false;
	}
	
	if(!this.isEnumerated(type)) {
		this.error(null, null,"The type \"" + type.name + "\" isn't enumerated");
		return false;
	}
	
	if(value == "") {
		this.error(null, null,"The value \""+value+"\" isn't valid");
		return false;
	}
	
	for(var i = 0 ; i < type.allowedValues.length ; ++i) {
		if(type.allowedValues[i][0] == value) {
			this.error(null, null,"Duplicated values are meaningless");
			return false;	
		}
	}
	
	type.allowedValues.push([value, description]);			
	return true;
};

/**
 * Deletes the allowed value at position index.
 * @param type a type
 * @param index the value index
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.deleteValue = function(type, index)
{			
	if(type == null) {
		this.error(null, null,"Cannot manipulate the null type");
		return false;
	}
	
	if(!this.isWriteable(type)) {
		this.error(null, null,"The type \"" + type.name + "\" isn't writeable");
		return false;
	}
	
	// Gets the allowed values
	var allowedValues = type.allowedValues;	// by reference
	
	if(index < 0 || index >= allowedValues.length) {
		this.error(null, null,"Index out of range");
		return false;
	}
	
	// Removes the value at index "position"
	allowedValues.splice(index,1);
	
	return true;
};

/**
 * Parses a type system descriptor. This is a non validating parser, which aims
 * at extracting the subset of the XML document matching the XML schema.
 * You should invoke this method to deserialize a type system descriptor: <br />
 * <code>
 * var ts = TypeSystem.parse(fileURL);
 * </code>
 * @param fileURL the file URL identifying the descriptor
 */
TypeSystem.parse = function(fileURL) {
	// Creates an empty type system			
	var ts = new TypeSystem();	
	ts.descriptors.length = 0;	// Removes the default (empty) descriptor	
				
	// Imports the the type system descriptor
	ts.importDescriptor("abs", fileURL.spec);
	
	// Returns the type system (ts.hasError == true iff an error occurred during the import process)
	return ts;
};

/**
 * Creates a new type system descriptor.
 * @param fileURL the descriptor base URI
 * @return a new descriptor
 */
TypeSystem.prototype.newDescriptor = function(fileURL)
{		
	// Iterates over the descriptors
	for(var i = 0 ; i < this.descriptors.length ; ++i)
		if(this.descriptors[i].fileURL && fileURL.equals(this.descriptors[i].fileURL)) {
			return this.descriptors[i];		
		}
	
	// Creates a new type system descriptor
	var desc = new TypeSystemDescriptor(fileURL);
	desc.parsing = true;
	
	// Adds it to the type system
	this.descriptors.push(desc);
				
	// Returns the descriptor
	return desc;			
};

/**
 * Adds an error.
 * @param descriptor reference to the descriptor where the error occurs
 * @param location xpointer expression locating the error
 * @param message the error human readable error
 */
TypeSystem.prototype.error = function(descriptor, location, message)
{
	this.hasError = true;
	if(this.errorListener)
		this.errorListener(descriptor ? descriptor.fileURL : null, location, message);
};

/**
 * Error listener invoked to display an error. Users may overwrite this method
 * to provide a custom error handling mechanism.
 * @param fileURL the nsIFileURL describing a type system descriptor (may be null)
 * @param location the location inside the type system descriptor
 * @param message an error message
 */
TypeSystem.prototype.errorListener = function(fileURL, location, message) {
	alert("In file: " + (fileURL ? "\"" + fileURL.spec + "\"" : "unknown") +"\n" +
	      "at position: " + (location ? location : "unknown") + "\n" +
	      message);
};

/**
 * Resolves a reference.
 * @param mode the reference mode
 * @param reference the reference
 * @return the fileURL (as nsIFileURL) or null if an error occurs
 */
TypeSystem.prototype.resolveReference = function(mode, reference) {
	if(mode == "abs" || mode == "rel") {
		var ios = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(Components.interfaces.nsIIOService);
                            
        try {
        	return ios.newURI(reference, null, this.descriptors[0] ? this.descriptors[0].fileURL : null);
        }catch(e) {
        	this.error(null,null,""+e);
        }
		
	} else {
		this.error(null, null, "Unknown reference mode");
	}
	
	return null;
}

/**
 * Imports a type system descriptor into this type system
 * @param mode the import mode
 * @param reference the reference
 */
TypeSystem.prototype.importDescriptor = function(mode, reference) {	

	// Clears every error condition
	this.hasError = false;
				
	// Annotates the old number of descriptors
	// If an error occurs, this can be used to remove the affected descriptors
	var descCount = this.descriptors.length;					
		
	var fileURL = this.resolveReference(mode, reference);
	
	if(fileURL == null) {
		return;
	}
		
	// Parses the type system descriptor
	this.parseDescriptor(fileURL);

	// Iterates over the types defined in the newly added descriptors
	// Resolves references to super type and check that the inheritance graph is
	// a tree. Note that a cycle may be added if and only if a (new) type A inherits
	// from a (new) type B which, in turn, inherits from A. Old types cannot cause
	// cycles.
	
	// Note: after the parsing, those fields contain the textual names,
	//       which should be replaced with references to the types				
	var isTree = true;
					
	for(var i = descCount ; i < this.descriptors.length ; ++i) {
		// The i-th descriptor (zero based)
		var desc = this.descriptors[i];
		
		// Iterates over the descriptor types
		for(var j in desc.types) {
						
			// A type
			var t = desc.types[j];
			
			// Hash set used to verify if a type is in a cycle
			var hash = {};
			
			hash[t.name] = true;
			
			while(t) {	// climb the inheritance tree, until reaches an old type
				// Skips this type if its parent was yet considered
				if(typeof(t.supertype) != "string")
					break;
				
				if(hash[t.supertype]) {
					this.error(t.descriptor,"\"" + t.name+"\" definition","Cyclic reference from \"" + t.name + "\" to \"" + t.supertype + "\"");
					isTree = false;
				}
												
				// The supertype
				var s = this.getType(t.supertype);
				
				if(s == null) {
					this.error(t.descriptor,"\"" + t.name+"\" definition","Undefined reference to \"" + t.supertype + "\"");
					t.supertype = this.uima_cas_TOP;	// uses uima.cas.TOP to let the process go ahead
					s = this.uima_cas_TOP;
				} else {
					// replaces the supertype name with an object reference
					t.supertype = s;
				}
				
				// Adds this type to its supertype list of subtypes
				s.subtypes.push(t);
				
				// Climbs the inheritance tree
				// If s is an old type or it was inspect inside a different path
				// then its parent isn't a string, so the loop will be broken
				t = s;
				hash[t.name] = true;
			}
		}
	}
	
	//// Now the type references are resolved,
	//// so you can use predicate isXXX
														
	if(isTree) {	
		// if the type system is a tree, performs the other checks
	 	// Traverse the tree, to check that the following conditions are met
	 	// 1) final types aren't inherited
	 	// 2) verify that there aren't feature with the same name in any path
	 	//    NOTE: this is a restriction respect to UIMA, since it allows
	 	//          homonym feature if they are defined equally
	 	// 3) normalizes features: removes elementType if the range isn't templatized
	 	//                         removes multireferencesallowed if the range isn't a sublclass of uima.cas.ArrayBase
	 	//                         if the range is uima.cas.FSArray and element type is primitive, then replaces it with uima.cas.<short name>Array
	 	// 4) computes the cross-references
	 	
	 	// The stack used to perform a deep traversal
	 	var stack = [this.uima_cas_TOP];
	 			 				 	
	 	while(stack.length != 0) 
	 	{
	 		// Removes the top type
	 		var top = stack.pop();	 		
	 		
	 		// Do not check types which aren't defined in the new descriptors
	 		if(top.descriptor && top.descriptor.parsing) {	// the && is needed to skip built-in types which have no descriptor			 						 						 		
		 		// Verify that a type doesn't extend a final one
		 		if(this.isFinal(top.supertype))
		 			this.error(top.descriptor,"\"" + top.name + "\" definition", "The type \""+ top.name + "\" extends the final type \"" + top.supertype.name + "\"");
		 			 				 						 		
				// Iterates over the features, in order to resolve references to types,
				// normalize them and verify that no redefinition occurs within the type itself or the inheritance path
				for(var h = 0 ; h < top.features.length ; ++h) {
					var f = top.features[h];
									
					// Resolves the range type
					var r = this.getType(f.rangeType);
					if(r == null) {
						this.error(top.descriptor,"the definition of the feature \"" +f.name+"\" inside the type \"" +top.name+"\"","Undefined reference to \""+f.rangeType+"\"");
						r = this.uima_cas_TOP;	// assumes uima.cas.TOP to let the process go ahead
					}	
					f.rangeType = r;
												
					// Resolves the element type
					if(f.elementType) {	// checks if the feature has an element type
						var e = this.getType(f.elementType);
						
						if(e == null) {
							this.error(top.descriptor,"the definition of the feature \"" +f.name+"\" inside the type \"" +top.name+"\"","Undefined reference to \""+f.elementType+"\"");
							e = this.uima_cas_TOP;	// assumes uima.cas.TOP to let the process go ahead
						}
						f.elementType = e;
					}
				
					this.normalizeFeature(f);	// works in place					
									
					// Verifies that there are no redefinition within the type
					for(var k = h + 1 ; k < top.features.length ; ++k) {
						if(f.name == top.features[k].name) {
							this.error(top.descriptor,"the definition of the feature \"" +f.name+"\" inside the type \"" +top.name+"\"","The feature \""+ f.name + "\" is redefined within the domain \"" + top.name + "\"");
						}
					}		
												
					// Verifies that there aren't clashes through the inheritance tree
					var supF = this.getFeature(top, f.name);	// inherited feature have priority on the local ones
																										
					if(supF && !supF.equals(f)) {
						this.error(top.descriptor,"the definition of the feature \"" +f.name+"\" inside the type \"" +top.name+"\"","There is a conflict with an inherited definition");
					}	
					
					// Adds a reference to the range type
					f.rangeType.addReference(top);
										
					// Adds a reference to the the element type (if any)
					if(f.elementType)
						f.elementType.addReference(top);													
				}	
			}
			
	 		// Pushes the subtypes into the stack
	 		stack = stack.concat(top.subtypes);
	 	}
 	}			 
		 
	// If there is any error, slices the new descriptors
	if(this.hasError) {
		// Toggles any reference from the newly created types
		for(var i = descCount ; i < this.descriptors.length ; ++i) {
			for(var j in this.descriptors[i].types) {
				var t = this.descriptors[i].types[j];
				
				for(var h = 0 ; h < t.features.length ; ++h) {
					var f = t.features[h];
					f.rangeType.toggleAllReferences(t);
					if(f.elementType)
						f.elementType.toggleAllReferences(t);
				}
				
				// Toggles the type from its parent subtypes list
				var s = t.supertype.subtypes;
				for(var h = 0 ; h < s.length ; ++h)
					if(s[h] == t) {
						s.splice(h,1);
						break;
					}
			}
			
			// Removes the imports
			for(var j = 0 ; j < this.descriptors[i].imports.length ; ++j) {
				--this.descriptors[i].imports[j].referencesCount;
			}
		}

		this.descriptors = this.descriptors.slice(0,descCount);		
	} else {
		// Terminate the parsing of the new descriptors
		for(var i = descCount ; i < this.descriptors.length ; ++i)
			delete(this.descriptors.parsing);
		
		// If the import isn't called by the parsing method
		// but by the user, then it must add a reference from
		// the default descriptor to the imported one	
		
		if(descCount != 0) {
			// Checks that the descriptor wasn't yet imported
			for(var h = 0 ; h < this.descriptors[0].imports.length ; ++h) 
				if(this.descriptors[0].imports[h].descriptor == this.descriptors[descCount])
					return;
			
			this.descriptors[0].imports.push({ "mode" : mode, "reference" : fileURL, "descriptor" : this.descriptors[descCount] });
		}
	}
};

/**
 * Reads a type system descriptor.
 * @param fileURL the file URL identifying the descriptor
 */
TypeSystem.prototype.readDescriptor = function(fileURL) {
	try {
		// Instantiates an XMLHttpRequest
		var req = new XMLHttpRequest();
		
		// Opens a connection
		req.open("GET", fileURL.spec, false);	// synchronous request
		
		// Disconnects on request finish
		req.setRequestHeader("connection","close");
		
		// By-passes cache
		req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; 
		
		// Cause the response to be treated as XML
		req.overrideMimeType("text/xml");
		
		// Sends the request (ie. download the [local] file)
		req.send(null);
		
		// Returns the XML file or null if an error occurs
		return req.responseXML;
	} catch(e) {
		return null;	// if an exception is raised, then return null
	}
};

/**
 * Parses a type system descriptor during the import process.
 * @param fileURL the file URL identifying the descriptor7
 * @return the descriptor
 */
TypeSystem.prototype.parseDescriptor = function(fileURL) {	
	// Adds a descriptor to the type system
	var desc = this.newDescriptor(fileURL);	// returns null if that descriptor is already there
		
	// Do not parse a descriptor twice
	if(desc.referencesCount++ > 0)
		return desc;			
				
	// Cyclic imports are allowed, but descriptors need not be parsed twice
		
	// Loads the descriptor
	var doc = this.readDescriptor(fileURL);			
	
	// Checks if the document was read
	if(doc == null) {
		this.error(newDescriptor,"global scope","Unable to load the type system descriptor");
		return desc;
	}
				
	// Gets a reference to the IO service
	var ios = Components.classes["@mozilla.org/network/io-service;1"]
	                    .getService(Components.interfaces.nsIIOService);
	
 	//// Parses the imports
 	// Gets the imports
 	var importIt = doc.evaluate("/uima:typeSystemDescription/uima:imports/uima:import",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null);	
 	
 	// Iterates over the imports
 	var imp;
 	var num = 0;	// num. of imports
 	while(imp = importIt.iterateNext()) {
 		
 		// Increments num
 		++num;
 		
 		// Reads the file URL of the descriptor to import
 		// TODO: add support for the attribute name and the datapath
 		var location = imp.getAttribute("location");
 		
 		if(!imp) {
 			this.error(desc,"imports list","Missing location attribute");
 			continue;	// skips the import
 		}
 		
 		var impURL;
 				 		
 		//// Parses the descriptor file URL
 		try {
 			// Creates the URI using the URI of the current descriptor as base UEI
 			var uri = ios.newURI(location, null, desc.fileURL); 
 			
 			// Verify that the URI is a file URI (ie. its scheme is "file")
 			impURL = uri.QueryInterface(Components.interfaces.nsIFileURL);
 			
 		} catch(e) {
 			this.error(desc,"Imports list","" + e);		 			
 			continue;	// Skips the import
 		}
 		
 		// Parses the descriptor
 		var imp = this.parseDescriptor(impURL);
 		
		// Annotate the import (checks that it wasn't yet imported)
		// TODO: support name attribute
		for(var h = 0 ; h < desc.imports.length ; ++h) {
			if(desc.imports[h].descriptor == imp)
				return;
		}
		
		desc.imports.push({"mode" : (location.indexOf("file:") == 0 ? "abs" : "rel"), "reference" : fileURL , "descriptor" : imp});
 	}
 	
 	//// Parses the metadata
 	// name
 	var node = doc.evaluate("/uima:typeSystemDescription/uima:name/text()",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
	if(node)
		desc.name = node.nodeValue;
 
  	// description
  	node = doc.evaluate("/uima:typeSystemDescription/uima:description/text()",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
  	if(node)
  		desc.description = node.nodeValue;
 		
  	// version
  	node = doc.evaluate("/uima:typeSystemDescription/uima:version/text()",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
  	if(node)
  		desc.version = node.nodeValue;
  
  	// vendor
  	node = doc.evaluate("/uima:typeSystemDescription/uima:vendor/text()",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null).iterateNext();
	if(node)
		desc.vendor = node.nodeValue;
 	
 	//// Parses the type definitions
 	num = 0;	// num. of types
 	// Gets the type definition
 	var importIt = doc.evaluate("/uima:typeSystemDescription/uima:types/uima:typeDescription",doc,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null);
 	var typeElement;
 	
 	// Iterates over the type descriptions
 	while(typeElement = importIt.iterateNext()) {
 		// Increments the num
 		++num;
 		
		// Gets the type name element
		var nameElement = typeElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"name")[0];

		// Gets the description element
		var descriptionElement = typeElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"description")[0];

		// Gets the supertypeName element
		var supertypeNameElement = typeElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"supertypeName")[0];
	
		// Gets the name name
		var name = nameElement ? nameElement.textContent : "";
					
		// Gets the description (may be null)
		var description =  descriptionElement ? descriptionElement.textContent : null;
		
		// Gets the supertype name
		var supertypeName = supertypeNameElement ? supertypeNameElement.textContent : null;
		
		// If the supertype isn't specified, generates an error (but assume uima.cas.TOP to continue the process)
		if(supertypeName == null) {
			supertypeName = "uima.cas.TOP";
			this.error(desc, "the #" + num + " type definition","Missing name element");
		}	
		
		// Checks if the type name is valid
		if(!Type.isValidName(name)) {
			this.error(desc, "\"" + name+"\" definition","Invalid type name \"" + name + "\"");
		}	
						
		// Creates a new type
		var t = new Type(name, description, supertypeName, desc);
						
		// Checks if the type was alredy defined
		if(this.getType(name)) {
			this.error(desc,"\"" + name+"\" definition","Redefinition of the type \"" + name + "\"");
		}
		
		// Adds the type to this descriptor list of types
		desc.types[name] = t;			
	
		//// Parses allowed values (only for enumerated types: you cannot use isEnumerated since the supertype isn't yet resolved)
		if(supertypeName == "uima.cas.String") {				
			
			// Iterates over the allowed values
 			var avIt = doc.evaluate("uima:allowedValues/uima:value[uima:string]",typeElement,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null);
			var avElement;
			
			// Hash set used to skip duplicates
			var values = {};
			
			while(avElement=avIt.iterateNext()) {
				var valueString = avElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"string")[0].textContent;
				var description = avElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"description")[0]; // may be undefined
				description = description ? description.textContent : null;	// extracts the description, if present

				if(values[valueString])
					continue;
					
				values[valueString] = true;

				// Pushes the value into the type description
				t.allowedValues.push([valueString, description]);
			}	
		}

		//// Parses features
		// Iterates over features
 		var featIt = doc.evaluate("uima:features/uima:featureDescription",typeElement,TypeSystem.nsResolver, XPathResult.ANY_TYPE, null);
		var featElement;
						
		num = 0;	// num. of features

		while(featElement = featIt.iterateNext()) {
			var featNameElement = featElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"name")[0];
			var featDescriptionElement = featElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"description")[0];
			var featRangeTypeNameElement = featElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"rangeTypeName")[0];
			var featElementTypeElement = featElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"elementType")[0];
			var featMultipleReferencesAllowedElement = featElement.getElementsByTagNameNS(TypeSystem.UIMA_NS,"multipleReferencesAllowed")[0];
								
			var err = false;	// do an error occur while parsing this feature
			
			var featName;
			if(featNameElement)	// Gets the feature name
				featName = featNameElement.textContent;
			else {
				this.error(desc,"feature list of the type \"" + name + "\"","Unnamed features aren't allowed");
				err = true;
			}
			
			if(!Feature.isValidShortName(featName)) {
				this.error(desc,"feature list of the type \"" + name + "\"","\"" + featName + "\" isn't a valid name");					
			}
			
			// Gets the type description if any, otherwise sets it to null
			var featDescription = featDescriptionElement ? featDescriptionElement.textContent : null;
			
			// Gets the range type name
			var featRangeTypeName = null;
			if(featRangeTypeNameElement) {
				featRangeTypeName = featRangeTypeNameElement.textContent;
			} else {
				this.error(desc,"definition of the feature \"" + featName + "\" inside the type \"" + name + "\"","Missing range type in the definition of \"" + featName + "\".");					
			}
			
			// Gets the elementType, otherwise sets it to uima.cas.TOP
			var featElementType = featElementTypeElement ? featElementTypeElement.textContent : "uima.cas.TOP";
			
			// Gets multipleReferencesAllowed, if any
			var featMultipleReferencesAllowed = false;
			
			if(featMultipleReferencesAllowedElement) {
				var text = featMultipleReferencesAllowedElement.textContent;
				
				if(text == "true") {
					featMultipleReferencesAllowed = true;
				} else if(text != "false") {
					this.error(desc,"definition of the feature \"" + featName + "\" inside the type \"" + name + "\"","The multipleReferencesAllowed isn't a valid boolean value");
					// don't set err, since this error don't prevent this feature to be added to the current type
				}
			}			
								
			// Adds this feature to the current type
			++num;
			if(!err) {
				var f = new Feature(featName, featDescription, featRangeTypeName, featElementType, featMultipleReferencesAllowed);
				t.features.push(f);
			}
		}			

		if(num != 0 && (supertypeName == "uima.cas.String")) {	// here I cannot use isFeatureFinal
			this.error(desc,"\""+name+"\" definition","The feature final type \""+name+"\" cannot have features");
		}	
 	}	
 	
 	return desc;	 	
};

/**
 * Removes an import.
 * @param index the index of the import to remove
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.removeImport = function(index) {
	if(index < 0 || index >= this.descriptors[0].imports.length) {
		this.error(null,null,"Index out of bound");
		return false;
	}
	
	var descriptors = [];
	descriptors.push([this.descriptors[0].imports[index].descriptor,1]);
	
	// Marks for deletetion the fist descriptor and all
	// one which it imports
	for(var i = 0 ; i < descriptors.length ; ++i) {
		var desc = descriptors[i][0];
		for(var h = 0 ; h < desc.imports.length ; ++h) {
			var j;
			for(j = 0 ; j < descriptors.length ; ++j) {
				if(descriptors[j][0] == desc.imports[h].descriptor) {
					++descriptors[j][1];
					break;		
				}
			} 
			
			if(j == descriptors.length) {
				descriptors.push([desc.imports[h].descriptor, 1]);
			}
		}
	}
	
	var checkDescriptor = function(type) {
		for(var i = 0 ; i < descriptors.length ; ++i) {
			if(type.descriptor == descriptors[i][0])
				return true;
		}
		
		return false;
	}
	
	// Check for each descriptor that the number of refereces
	// form the other descriptor to delete is equal to the number
	// of ingoing referecens and for every type that:
	// 1) its subtypes are defined in a descriptor to remove
	// 2) every reference come from those descriptor
	for(var i = 0 ; i < descriptors.length ; ++i) {
		if(descriptors[i][1] != descriptors[i][0].referencesCount) {
			this.error(null, null, "Cannot remove the descriptor \"" + descriptors[i][0].fileURL.spec +"\" which is imported by a descriptor not to be deleted");
			return false;
		}	
		
		for(var j in descriptors[i][0].types) {
			var t = descriptors[i][0].types[j];
			
			for(var h in t.references) {
				var r = t.references[h].type;
				
				if(!checkDescriptor(r)) {
					this.error(null, null, "A type is referenced by a type which is defined in a descriptor not to be deleted");
					return false;
				}
			}
			
			for(var h = 0 ; h < t.subtypes.length ; ++h) {
				var sub = t.subtypes[h];
				
				if(!checkDescriptor(sub)) {
					this.erro(null, null, "A type is extended by a type which is defined in a descriptor not to be deleted");
					return false;
				}
			}
		}
	}
	
	//// Removes the types and the descriptors
	// Iterates over the descriptors
	for(var i = 0 ; i < descriptors.length ; ++i) {
	
		// Iterates over the types
		for(var j in descriptors[i][0].types) {
			var t = descriptors[i][0].types[j];
			
			// Removes the type from its parent list of subtypes
			var parent = t.supertype;
			for(var h = 0 ; h < parent.subtypes.length ; ++h) {
				if(parent.subtypes[h] == t) {
					parent.subtypes.splice(h,1);
					break;
				}
			}
			
			// Toggles every reference generated by the type's features
			for(var h = 0 ; h < t.features.length ; ++h) {
				var f = t.features[h];
				
				f.rangeType.toggleAllReferences(t);
				
				if(f.elementType)
					f.elementType.toggleAllReferences(t);
			}
		}
		
		// Toggles the descriptor
		for(var h = 0 ; h < this.descriptors.length ; ++h)
			if(this.descriptors[h] == descriptors[i][0]) {
				this.descriptors.splice(h,1);
				break;
			}
	}
	
	// Toggles the import from the default descriptor (index = 0)
	for(var h = 0 ; h < this.descriptors[0].imports.length ; ++h) {
		if(this.descriptors[0].imports[h].descriptor == descriptors[0][0]) {
			this.descriptors[0].imports.splice(h,1);
			break;
		}
	}
	
	return true;
};

/**
 * Saves the type system to its current location.
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.save = function() {
	if(this.descriptors[0] && this.descriptors[0].fileURL) {
		return this.saveAs(this.descriptors[0].fileURL);
	} else {
		this.error(null,"saving the descriptor","Cannot save the typesystem.\nUnable to detect the previous location");
		return false;
	}
};

/**
 * Saves the type system to a user provided location.
 * @param fileURL the outputfile
 * @return true iff the operation succeeded
 */
TypeSystem.prototype.saveAs = function(fileURL) {
				
	// this method serializes only the first type descriptors
	// which collects the user defined types
	// Other descriptors are imported and not serialized			
	
	// The first descriptor
	var desc = this.descriptors[0];
	
	var doc = document.implementation.createDocument("", "", null);
	var prol = doc.createProcessingInstruction("xml",'version="1.0" encoding="UTF-8"');
	doc.appendChild(prol);
	var root = doc.createElementNS(TypeSystem.UIMA_NS,"uima:typeSystemDescription");
	root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns",TypeSystem.UIMA_NS);			
	doc.appendChild(root);
	
	// Serialize name, description, version and vendor if any
	if(desc.name !== null) {	// name
		var element = doc.createElementNS(TypeSystem.UIMA_NS,"uima:name");
		var text = doc.createTextNode(desc.name);
		element.appendChild(text);
		root.appendChild(element);			
	}
	
	if(desc.description !== null) {	// description
		var element = doc.createElementNS(TypeSystem.UIMA_NS,"uima:description");
		var text = doc.createTextNode(desc.description);
		element.appendChild(text);
		root.appendChild(element);			
	}
	
	if(desc.version !== null) {
		var element = doc.createElementNS(TypeSystem.UIMA_NS,"uima:version");
		var text = doc.createTextNode(desc.version);
		element.appendChild(text);
		root.appendChild(element);			
	}
	
	if(desc.vendor !== null) {
		var element = doc.createElementNS(TypeSystem.UIMA_NS,"uima:vendor");
		var text = doc.createTextNode(desc.vendor);
		element.appendChild(text);
		root.appendChild(element);			
	}
			
	// Serializes the import	
	var imports = doc.createElementNS(TypeSystem.UIMA_NS,"uima:imports");
		
	// Iterates over the import
	for(var h = 0 ; h < desc.imports.length ; ++h) {
		var imp = doc.createElementNS(TypeSystem.UIMA_NS,"uima:import");
		imports.appendChild(imp);
		if(desc.imports[h].mode == "name") {	// name import
			imp.setAttribute("name", desc.imports[h].fileURL);
		} else if(desc.imports[h].mode == "abs") {	// absolute file URL import
			imp.setAttribute("location", desc.imports[h].reference.spec);
		} else {	// relative file URL import
			// Since the original descriptor imported this descriptor with a relative path
			// then compute the relative URI respect to the destination URI
			imp.setAttribute("location",fileURL.QueryInterface(Components.interfaces.nsIFileURL).getRelativeSpec(desc.imports[h].reference));
		}
	}
	
	root.appendChild(imports);
			
	var types = doc.createElementNS(TypeSystem.UIMA_NS,"uima:types");
	root.appendChild(types);
				
	// Iterates over the types and serializes them
	for(var i in desc.types) {
		var t = desc.types[i];	// one of the types in this descriptor			
		
		// The type description
		var type = doc.createElementNS(TypeSystem.UIMA_NS,"uima:typeDescription");
		types.appendChild(type);
		
		// Serializes type name
		var el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:name");
		var text = doc.createTextNode(t.name);
		el.appendChild(text);
		type.appendChild(el);


		// Serializes type description (if any)
		if(t.description !== null) {
			el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:description");
			text = doc.createTextNode(t.description);
			el.appendChild(text);
			type.appendChild(el);		
		}	
		
		
		// Serializes supertype name
		el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:supertypeName");
		text = doc.createTextNode(t.supertype.name);
		el.appendChild(text);
		type.appendChild(el);	
		
		// The type features
		var features = doc.createElementNS(TypeSystem.UIMA_NS, "uima:features");
		type.appendChild(features);
						
		
		// Iterates over the features
		for(var j = 0 ; j < t.features.length ; ++j) {
			var f = t.features[j];	// the j-th (zero based) feature of the current type

			// The feature description
			var feature = doc.createElementNS(TypeSystem.UIMA_NS,"uima:featureDescription");
			features.appendChild(feature);
			
			// Serializes feature name
			el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:name");
			text = doc.createTextNode(f.name);
			el.appendChild(text);
			feature.appendChild(el);
			
			// Serializes the description (if any)
			if(f.description !== null) {
				el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:description");
				text = doc.createTextNode(f.description);
				el.appendChild(text);
				feature.appendChild(el);
			}
								
			// Serializes range type name
			el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:rangeTypeName");
			text = doc.createTextNode(f.rangeType.name);
			el.appendChild(text);
			feature.appendChild(el);
		
			// Serializes elementType (if any)
			if(f.elementType !== null) {
				el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:elementType");
				text = doc.createTextNode(f.elementType.name);
				el.appendChild(text);
				feature.appendChild(el);
			}					
			
			// Serializes the multiple references allowed (if any)
			if(f.multipleReferencesAllowed !== null) {
				el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:multipleReferencesAllowed");
				text = doc.createTextNode(f.multipleReferencesAllowed);
				el.appendChild(text);
				feature.appendChild(el);	
			}						
		}
						
		// Serialized the allowed values, if any
		if(t.allowedValues.length != 0) {
			var av = doc.createElementNS(TypeSystem.UIMA_NS,"uima:allowedValues");
			type.appendChild(av);					
			
			// Iterates over the allowed values
			for(var j = 0 ; j < t.allowedValues.length ; ++j) {
				var v = doc.createElementNS(TypeSystem.UIMA_NS,"uima:value");
				av.appendChild(v);						

				// Serializes value
				el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:string");
				text = doc.createTextNode(t.allowedValues[j][0]);
				el.appendChild(text);
				v.appendChild(el);

				// Serializes description (if any)
				if(t.allowedValues[j][1] !== null) {
					el = doc.createElementNS(TypeSystem.UIMA_NS,"uima:description");
					text = doc.createTextNode(t.allowedValues[j][1]);
					el.appendChild(text);
					v.appendChild(el);	
				}				
			}									
		}
	}
					
	try {
		// Instantiates an XML serializer
		var serializer = new XMLSerializer();
		
		// Instantiates a file output stream
		var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
		               .createInstance(Components.interfaces.nsIFileOutputStream);
		 
		var file = fileURL.QueryInterface(Components.interfaces.nsIFileURL).file;
		 
		foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);   // write, create, truncate
		serializer.serializeToStream(doc, foStream, "UTF-8"); 
		foStream.close();
	} catch(e) {
		this.error(null,"writing the file",e);
		return false;
	}
	
	// Updates the descriptor fileURL
	desc.fileURL = fileURL;
	
	return true;
};

// UIMA namespace
TypeSystem.UIMA_NS = "http://uima.apache.org/resourceSpecifier";

// Namespace Resolver for XPATH
TypeSystem.nsResolver = function nsResolver(prefix) {
	var ns = {
		'xhtml' : 'http://www.w3.org/1999/xhtml',
		'uima'  : TypeSystem.UIMA_NS
	};
	
	return ns[prefix] || null;
};

/**
 * A type system descriptor.
 * @param fileURL the descriptor base uri (as a file URL)
 */
function TypeSystemDescriptor(fileURL)
{
	// This descriptor file URL
	this.fileURL = fileURL;
	
	// This type system  descriptor name, description, version and vendor (null by default)
	this.name = null;
	this.description = null;
	this.version = null;
	this.vendor = null;
	
	// Number of references to this descriptor
	this.referencesCount = 0;
				
	// A list of imports; a list of tuples {mode, reference, descriptor}.
	// if mode is name, then value is a name
	// if mode is rel/abs, then value is an absolute uri which must be translate to a relative one in first case
	this.imports = [];
	
	// The type dictionary. It let us to access quickly to the defined types.
	this.types = {};
}

/**
 * Gets a type defined in this type descriptor with a given name.
 * @param name the type name
 * @return the type name or null if there isn't one
 */
TypeSystemDescriptor.prototype.getType = function(name) {
	return this.types[name] || null;
	/*
	 * Remember that the JavaScript OR operator,
	 * returns the first argument if it is convertible
	 * to true, otherwise returns the second argument
	 */
};

/**
 *	A type in a type system.
 * @param name the type name
 * @param description the type description (may be null)
 * @param supertype the supertype
 * @param descriptor the descriptor where this type is defined
 */
function Type(name, description, supertype, descriptor) 
{
	this.name = name;
	this.description = description;
	this.supertype = supertype;
						
	this.descriptor = descriptor;
					
	// subtypes
	this.subtypes = [];
	
	// features
	this.features = [];
	
	// allowed values: pairs string/description; only for enumerated types
	this.allowedValues = [];
	
	// referencing types
	// Although it is an array, it is used as an hash set
	// Thus, you should iterate over if whit for(var i in references)
	// since the indeces may not be contiguous
	this.references = [];
}

/**
 * Toggles a reference to this type. Ignores self references.
 * @param type the referencing type
 */
Type.prototype.toggleReference = function(type) {
	if(this == type)
		return;
		
	for(var i in this.references) {
		var r = this.references[i];
		if(r.type == type) {
			if(--r.count <= 0)
				delete(this.references[i]);
			
			return;
		}
	}
};

/**
 * Toggles all references to this type. Ignores self references.
 * @param type the referencing type
 */
Type.prototype.toggleAllReferences = function(type) {
	if(this == type)
		return;
		
	for(var i in this.references) {
		var r = this.references[i];
		if(r.type == type) {
			delete(this.references[i]);
			return;
		}
	}
};
		
/**
 * Adds a reference to this type. Ignores self references.
 * @param type the referencing type
 */
Type.prototype.addReference = function(type) {
	if(this == type)
		return;
		
	for(var i in this.references) {
		var r = this.references[i];
		if(r.type == type) {
			++r.count;
			
			return;
		}
	}
	
	this.references.push({"type" : type, "count" : 1});
};
		
/**
 * Gets a features with given name declared by this type.
 * @param name the feature name
 * @return the feature, or null if there isn't one
 */	
Type.prototype.getDeclaredFeature = function(name)
{
	for(var i = 0 ; i < this.features.length ; ++i)
		if(this.features[i].name == name)
			return this.features[i];	
			
	return null;
};

/**
 * Gets the namespace associated to the given type name.
 * @param name the plain text name
 * @return the namespace
 */
Type.getNamespace = function(name) {
	// since the klien star is greedy, the 
	// first subexpression captures every character
	// until the last . (if any)
	const pattern = /^(.*)\./;
	return pattern.exec(name)[1] || "";
};

/**
 * Gets the short associated to the given type name
 * @param name the plain text name
 * @return the short name
 */
Type.getShortName = function(name) {
	const pattern = /([^\.]*)$/;
	return pattern.exec(name)[1] || "";
};

/**
 * Checks if a name is valid.
 * @param name a candidate type name
 */
Type.isValidName = function(name) {
	// Defines the pattern
	var pattern = /^[a-z]\w*(\.[a-z]\w*)*$/i;
					
	return (pattern.test(name) && name.indexOf("uima.") != 0);
};

/**
 * A feature.
 * @param name this feature name
 * @param description this feature description
 * @param rangeTypeName this feature range type
 * @param elementType this feature element type
 * @param multipleReferencesAllowed multiple references allowed
 */
function Feature(name, description, rangeType, elementType, multipleReferencesAllowed)
{
	this.name = name;
	this.description = description;
	this.rangeType = rangeType;
	this.elementType = elementType;
	this.multipleReferencesAllowed = multipleReferencesAllowed;
}

/**
 * Checks if a feature short name is valid
 * @param shortName the feature short name
 * @return true iff the given parameter is a valid short name
 */
Feature.isValidShortName = function (shortName)
{
	// Defines the pattern
	var pattern = /^[a-z]\w*$/i;
					
	return (pattern.test(shortName));
};

/**
 * Compares two (normalized) features.
 * @param f the second term of comparison
 * @return true iff the features are equals
 */
Feature.prototype.equals = function (f) {
	return this.name == f.name && this.rangeType == f.rangeType && this.elementType == f.elementType && this.multipleReferencesAllowed == f.multipleReferencesAllowed;
};
